﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Principal;
using System.Text.RegularExpressions;
using System.Web.Mvc;
using System.Web.Security;
using ActivateYourGlutes.Data;
using ActivateYourGlutes.Infrastructure;
using ActivateYourGlutes.Infrastructure.Logging;
using ActivateYourGlutes.Infrastructure.MembershipServices;
using ActivateYourGlutes.Helpers;
using ActivateYourGlutes.Payloads.Account;
using ActivateYourGlutes.Payloads.Shared;
using ActivateYourGlutes.Services;
using ActivateYourGlutes.Services.Model;

namespace ActivateYourGlutes.Controllers
{

    [HandleError]
    public partial class AccountController : LocalisingController
    {
        private readonly IAccountProvider _accountProvider;
        private readonly IMeasurementTypeProvider _measurementTypeProvider;
        private readonly IBodyProvider _bodyProvider;
        private readonly ILookupProvider _lookupProvider;
        private readonly IFormsAuthentication _formsAuthentication;
        
        // This constructor is not used by the MVC framework but is instead provided for ease
        // of unit testing this type. See the comments at the end of this file for more
        // information.
        public AccountController(IAccountProvider accountProvider,
            IBodyProvider bodyProvider,
            ILookupProvider lookupProvider,
            IMeasurementTypeProvider measurementTypeProvider,
            IFormsAuthentication formsAuth,
            ILogger logger) : base(accountProvider, logger)
        {
            _formsAuthentication = formsAuth ?? new FormsAuthenticationService();
            _measurementTypeProvider = measurementTypeProvider;
            _accountProvider = accountProvider;
            _bodyProvider = bodyProvider;
            _lookupProvider = lookupProvider;
        }

        /// <summary>
        /// Creates a link that is forced onto the standard http port (for example redirectin after login).
        /// </summary>
        private string InsecureUrl(string action, string controller)
        {
            return LinkHelpers.InsecureActionLink(ControllerContext.RequestContext,
                                                           ControllerContext.HttpContext, null, action, controller);
        }

        protected override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            base.OnActionExecuting(filterContext);
            if (filterContext.HttpContext.User.Identity is WindowsIdentity)
            {
                throw new InvalidOperationException("Windows authentication is not supported.");
            }
        }

        #region Lookup generators

        /// <summary>
        /// Gets the units of lengths available for localisation as select list items
        /// </summary>
        private IEnumerable<SelectListItem> GetLengthsAsSelectList()
        {
            return new SelectList(_lookupProvider.UnitsOfLength, "UnitOfLengthId", "Description");
        }

        /// <summary>
        /// Gets the units of lengths available for localisation as select list items and marks the currently selected length
        /// for the given account as selected.
        /// </summary>
        private IEnumerable<SelectListItem> GetLengthsAsSelectList(Account account)
        {
            return new SelectList(_lookupProvider.UnitsOfLength, "UnitOfLengthId", "Description", account.UnitOfLength.UnitOfLengthId);
        }

        /// <summary>
        /// Gets the units of weight available for localisation as select list items
        /// </summary>
        private IEnumerable<SelectListItem> GetWeightsAsSelectList()
        {
            return new SelectList(_lookupProvider.UnitsOfWeight, "UnitOfWeightId", "Description");
        }

        /// <summary>
        /// Gets the units of weight available for localisation as select list items and marks the currently selected unit
        /// for the given account as selected.
        /// </summary>
        private IEnumerable<SelectListItem> GetWeightsAsSelectList(Account account)
        {
            return new SelectList(_lookupProvider.UnitsOfWeight, "UnitOfWeightId", "Description", account.UnitOfWeight.UnitOfWeightId);
        }

        #endregion

        #region Miscellaneous

        public virtual ActionResult TermsAndConditions()
        {
            return View();
        }

        public virtual ActionResult Privacy()
        {
            return View();
        }

        public virtual ActionResult Unauthorized()
        {
            return View();
        }

        #endregion

        #region Account details

        [RequireSsl]
        [Authorize(Roles = "User")]
        public virtual ActionResult Index()
        {
            var account = _accountProvider.CurrentAccount(User);

            var checkedKeys = new List<MeasurementType>();
            if (account.PrimaryMeasurementType != null)
                checkedKeys.Add(account.PrimaryMeasurementType);
            if (account.SecondaryMeasurementType != null)
                checkedKeys.Add(account.SecondaryMeasurementType);
            SetIndexViewdata(account, checkedKeys);
            return View();
        }

        /// <summary>
        /// Updates an account using the membership provider and in our application database.
        /// </summary>
        [RequireSsl]
        [Authorize(Roles = "User")]
        [AcceptVerbs(HttpVerbs.Post)]
        [ValidateAntiForgeryToken]
        public virtual ActionResult Index(FormCollection formCollection)
        {
            var account = _accountProvider.CurrentAccount(User);
            var measurementTypes = _measurementTypeProvider.MeasurementTypes(account);
            var checkedKeys = new List<MeasurementType>();
            var email = formCollection["Email"];
            
            foreach (var type in measurementTypes)
            {
                var keyInForm = formCollection[type.ToFormName()];
                var isChecked = keyInForm != "false" && !String.IsNullOrEmpty(keyInForm);
                if (isChecked)
                    checkedKeys.Add(type);
            }

            if (checkedKeys.Count <= 2)
            {
                var unitOfLength =
                    _lookupProvider.UnitsOfLength.Where(
                        ul => ul.UnitOfLengthId == int.Parse(formCollection["UnitForLengthId"])).First();
                var unitOfWeight =
                    _lookupProvider.UnitsOfWeight.Where(
                        uw => uw.UnitOfWeightId == int.Parse(formCollection["UnitForWeightId"])).First();
                try
                {
                    _accountProvider.UpdateAccount(account, email, DateTime.ParseExact(formCollection["DateOfBirth"], LocaleSettings.DateFormat,
                                                          CultureInfo.InvariantCulture),
                                               unitOfLength,
                                               unitOfWeight,
                                               checkedKeys);
                }
                catch (ValidationException ex)
                {
                    ex.AddToModelState(ViewData.ModelState);
                }
                account = _accountProvider.CurrentAccount(User);
                var viewdata = SetIndexViewdata(account, checkedKeys);

                if (ModelState.IsValid)
                {
                    viewdata.Success = "Your account has been updated";
                    ClearLocaleSettings();
                }
            }
            else
            {
                var viewdata = SetIndexViewdata(account, checkedKeys);
                viewdata.HorizontalMeasurementTypesPayload.Error = "You can select upto two items";
            }

            
            return View();
        }

        /// <summary>
        /// Sets the view data required to display the account index
        /// </summary>
        private IndexPayload SetIndexViewdata(Account account, IEnumerable<MeasurementType> checkedKeys)
        {
            var url = Url.Action(MVC.Goal.Index());
            var message = "You can choose up to two key indicators to track on your homepage. We recommend also setting <a href=\"" +
                url +
                "\">goals</a> for these indicators to help you achieve your results.";

            var horizontalMeasurementTypesPayload = new HorizontalMeasurementTypesPayload
                                                        {
                                                            MeasurementTypes = _measurementTypeProvider.MeasurementTypes(account),
                                                            SelectedTypes = checkedKeys,
                                                            Error = null,
                                                            Message = message
                                                        };

            ViewData.Model = new IndexPayload
                                 {
                                     Account = account,
                                     Email = _accountProvider.EmailForAccount(account),
                                     UnitsOfLength = GetLengthsAsSelectList(account),
                                     UnitsOfWeight = GetWeightsAsSelectList(account),
                                     HorizontalMeasurementTypesPayload = horizontalMeasurementTypesPayload,
                                     Success = null,
                                     LocaleSettings = LocaleSettings
                                 };

            return (IndexPayload)ViewData.Model;
        }

        #endregion

        #region Logon and log off

        [RequireSsl]
        public virtual ActionResult LogOn()
        {
            return View();
        }

        [RequireSsl]
        [AcceptVerbs(HttpVerbs.Post)]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
            Justification = "Needs to take same parameter type as Controller.Redirect()")]
        [ValidateAntiForgeryToken]
        public virtual ActionResult LogOn(string userName, string password, bool rememberMe, string returnUrl)
        {
            try
            {
                _accountProvider.ValidateLogOn(userName, password);
            }
            catch (ValidationException ex)
            {
                ex.AddToModelState(ViewData.ModelState);
                return View();
            }

            _formsAuthentication.SignIn(userName, rememberMe);
            ClearLocaleSettings();
            if (!String.IsNullOrEmpty(returnUrl))
            {
                return Redirect(returnUrl);
            }
            
            return Redirect(InsecureUrl("Index", "Home"));
        }

        public virtual ActionResult LogOff()
        {

            _formsAuthentication.SignOut();

            return RedirectToAction("Index", "Home");
        }

        #endregion        

        #region Registration

        [RequireSsl]
        public virtual ActionResult Register()
        {
            SetRegisterViewData();
            return View();
        }

        /// <summary>
        /// Creates an account using the ASP.Net membership provider and in our database. Needs work as if the database portion
        /// fails the account currently remains, for all intents and purposes, orphaned in the membership provider.
        /// </summary>
        [RequireSsl]
        [AcceptVerbs(HttpVerbs.Post)]
        [ValidateAntiForgeryToken]
        public virtual ActionResult Register(string dateOfBirth, string dateFormat, string userName, string email, string password, string confirmPassword, int unitOfLength, int unitOfWeight, bool acceptTerms)
        {
            ViewData["PasswordLength"] = _accountProvider.MinPasswordLength;

            DateTime? dob = null;
            try
            {
                if (!String.IsNullOrEmpty(dateOfBirth))
                    dob = DateTime.ParseExact(dateOfBirth, "M-d-yyyy", CultureInfo.InvariantCulture);
            }
            catch (Exception)
            {
                dob = null;
            }

            if (userName != null)
                userName = userName.Trim();

            try
            {
                _accountProvider.ValidateRegistration(userName, email, password, confirmPassword, acceptTerms, dob);
            }
            catch (ValidationException ex)
            {
                ex.AddToModelState(ViewData.ModelState);
                SetRegisterViewData();
                return View();
            }

            // Attempt to register the user
            try
            {
                var unitOfWeightObject = _lookupProvider.UnitsOfWeight.Where(uw => uw.UnitOfWeightId == unitOfWeight).First();
                var unitOfLengthObject = _lookupProvider.UnitsOfLength.Where(ul => ul.UnitOfLengthId == unitOfLength).First();

                string parsedDateFormat = "dd-MM-yyyy";
                if (dateFormat == "month-day-year")
                    parsedDateFormat = "MM-dd-yyyy";

                _accountProvider.Create(userName, password, email, dob.Value, unitOfLengthObject, unitOfWeightObject, parsedDateFormat);
                _formsAuthentication.SignIn(userName, false /* createPersistentCookie */);
                ClearLocaleSettings();
                return Redirect(InsecureUrl("Index", "Home"));
            }
            catch (ValidationException ex)
            {
                ex.AddToModelState(ViewData.ModelState);
                // If we got this far, something failed, redisplay form
                SetRegisterViewData();
                return View();
            }
        }

        private void SetRegisterViewData()
        {
            ViewData.Model = new RegisterPayload
                                 {
                                     MinimumPasswordLength = _accountProvider.MinPasswordLength,
                                     UnitsOfLength = GetLengthsAsSelectList(),
                                     UnitsOfWeight = GetWeightsAsSelectList()
                                 };
        }

        #endregion

        #region Password management

        [RequireSsl]
        [Authorize]
        public virtual ActionResult ChangePassword()
        {

            ViewData["PasswordLength"] = _accountProvider.MinPasswordLength;

            return View();
        }

        [RequireSsl]
        [Authorize]
        [AcceptVerbs(HttpVerbs.Post)]
        [ValidateAntiForgeryToken]
        public virtual ActionResult ChangePassword(string currentPassword, string newPassword, string confirmPassword)
        {

            ViewData["PasswordLength"] = _accountProvider.MinPasswordLength;
            
            try
            {
                if (_accountProvider.ChangePassword(User.Identity.Name, newPassword, currentPassword, confirmPassword))
                {
                    return RedirectToAction("ChangePasswordSuccess");
                }
                ModelState.AddModelError("_FORM", "The current password is incorrect or the new password is invalid.");
                return View();
            }
            catch (ValidationException ex)
            {
                ex.AddToModelState(ViewData.ModelState);
                return View();
            }
            catch
            {
                ModelState.AddModelError("_FORM", "The current password is incorrect or the new password is invalid.");
                return View();
            }
        }

        [RequireSsl]
        public virtual ActionResult ChangePasswordSuccess()
        {
            return View();
        }

        #endregion        

    }

    
}
